
	/***********************************************************************************************

		@API
		Class: jsl.Url (Liquid URL!)

			A jsl.Url represents an URL in the Liquid world (understood by Liquid) without depend too much from RFC standards
			and without have the necessity to be too generic and too complicated.

		Static:
			b				valid					(url)
			url			scheme				(id, url*)

		Constructor:
			Url			Url						(url)

		Methods:
			pt			protocol			()
			sch			scheme				()
			host		host					()
			port		port					()
			path		path					()
			name		resource			()
			query		query					()
			frg			fragment			()

			params	params				()
			sgs			segments			()

			data		data					()
			Url			parent				()
			s				toString			()


		Url types understood by Liquid
			"http:"			"//" 	host [ ":" port ] [ path ] [ "?" query ] [ "#" fragment ]
			"https:"		"//" 	host [ ":" port ] [ path ] [ "?" query ] [ "#" fragment ]
			"ws:" 			"//" 	host [ ":" port ] [ path ] [ "?" query ]
      "wss:" 			"//"	host [ ":" port ] [ path ] [ "?" query ]
			"file:"			"//" [host] "/" 					path	 [ "?" query ]
			"absract:"	"//" 										[ path ] [ "?" query ] [ "#" fragment ]

		Simple encoding:
			No encoding is performed, all the URL parts are stored without modifications.
			The encoding is executed outside the class.

		Normalization:
			left space trim
			schema, host lowercase
			host ip zero normalized
			default port omitted
			query string after host or port is permitted and later normalized with ../?
			file schema ending with [/] is sliced

		Note:
			HTTP URLs can be written also on the form: http(method)://.. The method part will be ignored

			WebSocket URLs do not have the fragment part (from the WS standard API).
			File URLs do not have the query and fragment part.

			The normalization is Liquid specific.

			[query] must be a sequence of parameters of the form p1=v1&p2=v2..
			The URL/URI standards are more generic on the query string.

			Some new terms are used: [protocol], [resource] and [segments].

			The fragment is not part of the URL standard but of the URI standard.

			Url parts:
				scheme:		can be only http, https, ws, wss, file
				host:			is restricted to the RFC1738 rules
				path:			no restrictions beacuse a path can have any characters (the characters outside the ASCII will be encoded out of here)
				query:		restricted syntax to allow the parsing of the parameters
				fragment:	no restrictions beacuse a path can have any characters (the characters outside the ASCII will be encoded out of here)

	************************************************************************************************/

  //--------------------------------------------------------------------------------
	jsl.Class("jsl.Url", {

		//static data
		//--------------------------------------------------------------------------------
		$__rts: {},
		$__dps:	{ http: 80, https: 443, ws: 80, wss: 443, file: 0 }, //default ports
		$__re: 	/^\s*(https?(?:\((?:get|post|put|delete)\))?|[^\s:\/]+):\/\/([^:\/?#]+)?(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/i,

		//default data
		//--------------------------------------------------------------------------------
		__sch: "",
		__hst: "",
		__pth: "",
		__qry: "",
		__frg: "",

		//static methods
		//--------------------------------------------------------------------------------
		$valid: function(url) {
			var b, dt, nurl, prt, id//!
			if(arguments.length !== 1)	throw jsl.ParamError()//!

			b = this.isClassOf(url)

			if(!b) {

				b = jsl.isString(url)

				if(b) {

					prt 	= this.prototype
					nurl 	= prt.__parseScheme(url)
					b			= nurl

					if(b) {

						b = false
						if((dt = nurl.match(this.__re))) {

							id = dt[1].toLowerCase()

							if(!id.search(/https?/)) {

								b = true
								if(id.indexOf(":") > -1) b = !!id.match(/^https:?(get|post|put|delete)/)
								if(b) 					b = dt[2] && prt.__parseHost(dt[2])
								if(b && dt[3]) 	b = prt.__parsePort	(dt[3])
								if(b && dt[5]) 	b = prt.__parseQuery	(dt[5])
							}

							else if(id === "ws" || id === "wss") {
								b = dt[2] && prt.__parseHost(dt[2])
								if(b && dt[3]) b = prt.__parsePort	(dt[3])
								if(b && dt[5]) b = prt.__parseQuery	(dt[5])
								if(b && dt[6]) b = false
							}

							else if(id === "file") {
								b = !dt[2] || prt.__parseHost(dt[2])
								if(b && dt[3]) 	b = false
								if(b &&!dt[4])	b = false
								if(b && dt[6]) 	b = false
							}
						}
					}
				}
			}

			return !!b
		},
		//--------------------------------------------------------------------------------
		$scheme: function(id, url) {
			var v, mth, nurl//!
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			jsl.validate(id, "sr/^[^\\s:\\/]+$", "id")//!

			if(arguments.length > 1) {
				try 			{ nurl = jsl.Url(url) }
				catch(e) 	{ /*!*/throw jsl.ParamError("BadValue:url:url")/*!*/ }

				mth = (url + "").match(/^https?\((get|post|put|delete)\)/i)
				mth = mth && "(" + mth[1].toLowerCase() + ")" || ""

				nurl = nurl.__sch + mth + "://" + nurl.__hst +
							(nurl.__prt !== this.__dps[nurl.__sch] ? ":" + nurl.__prt : "") +
							(nurl.__pth ? "/" + nurl.__pth : "")

				this.__rts[id] = nurl.match(/^(.+?)\/?$/)[1] + "/"
				v = this
			}
			else
				v = this.__rts[id] || null

			return v
		},
		//Constructor
    //-------------------------------------------------------------------------------
		Url: function(url) {
			var Url, s, ls, a, l, dt, id, v, qy, pt

			Url = jsl.Url//!
			if(arguments.length !== 1)	throw jsl.ParamError()//!

			if(Url.isClassOf(url)) {

				this.__ptc = url.__ptc
				this.__sch = url.__sch
				this.__hst = url.__hst
				this.__prt = url.__prt
				this.__pth = url.__pth
				this.__qry = url.__qry

				this.__pms = url.params()
				this.__sms = url.segments()
			}
			else {//!
				if(!Url.valid(url))	throw jsl.ParamError("BadValue:url")//!

				dt = this.__parseScheme(url).match(Url.__re)

				//scheme
 	   		//--------------------------------
				this.__sch = dt[1].toLowerCase()
				pt = this.__sch.slice(0, 2)

				if(pt === "ht")
					this.__sch = this.__sch.match(/(https?)/)[1]

				if(pt === "ht")				this.__ptc = "http"
				else if(pt === "ws")	this.__ptc = "ws"
				else									this.__ptc = "file"

				//host
 	   		//--------------------------------
				if(!dt[2])
					this.__hst = "localhost"
				else {
					s = this.__parseHost(dt[2])//!
					if(!s)	throw jsl.ParamError("SyntaxError:url(host)")//!
					this.__hst = s.toLowerCase()
				}

				//port
 	   		//--------------------------------
				if(dt[3]) {
					s = this.__parsePort(dt[3])//!
					if(!s)	throw jsl.ParamError("BadValue:url(port)")//!
					this.__prt = +s
				}
				else
					this.__prt = Url.__dps[this.__sch]

				//path
 	   		//--------------------------------
				if(dt[4]) {
					s = dt[4]
					this.__pth = this.__sch !== "file" || s.slice(-1) !== "/" ? s : s.slice(0, -1)
					this.__sms = this.__pth.split("/")

					if(this.__sms[this.__sms.length - 1] == null)
						this.__sms[this.__sms.length - 1] = ""
				}
				else
					this.__sms = []

				//query
 	   		//--------------------------------
				if(dt[5]) {
					s = this.__parseQuery(dt[5])//!
					if(!s)	throw jsl.ParamError("SyntaxError:url(query)")//!
					this.__pms = {}

					qy = []
					ls = s.split("&")
					for(a = 0, l = ls.length; a < l; a++) {

						ls[a] = ls[a].split("=")
						id		= ls[a][0].replace(/^\s+|\s+$/g, "")
						v			= ls[a].slice(1).join("=") || ""

						this.__pms[id] = v
						qy.push(id + "=" + v)
					}

					this.__qry = qy.join("&")
				}
				else
					this.__pms = {}

				//fragment
 	   		//--------------------------------
				if(dt[6])
					this.__frg = dt[6]
			}
		},
		//instance methods
    //-------------------------------------------------------------------------------
		protocol: function() {//!
			if(arguments.length)	throw jsl.ParamError()//!
			return this.__ptc
		},
    //-------------------------------------------------------------------------------
		scheme: function() {//!
			if(arguments.length)	throw jsl.ParamError()//!
			return this.__sch
		},
    //-------------------------------------------------------------------------------
		host: function() {//!
			if(arguments.length)	throw jsl.ParamError()//!
			return this.__hst
		},
    //-------------------------------------------------------------------------------
		port: function() {//!
			if(arguments.length)	throw jsl.ParamError()//!
			return this.__prt
		},
    //-------------------------------------------------------------------------------
		path: function() {//!
			if(arguments.length)	throw jsl.ParamError()//!
			return this.__pth
		},
    //-------------------------------------------------------------------------------
		resource: function() {//!
			if(arguments.length)	throw jsl.ParamError()//!
			return this.__sms[this.__sms.length - 1] || ""
		},
    //-------------------------------------------------------------------------------
		query: function() {//!
			if(arguments.length)	throw jsl.ParamError()//!
			return this.__qry
		},
    //-------------------------------------------------------------------------------
		fragment: function() {//!
			if(arguments.length)	throw jsl.ParamError()//!
			return this.__frg
		},
    //-------------------------------------------------------------------------------
		params: function() {//!
			if(arguments.length)	throw jsl.ParamError()//!
			return jsl.Object.clone(this.__pms)
		},
    //-------------------------------------------------------------------------------
		segments: function() {//!
			if(arguments.length)	throw jsl.ParamError()//!
			return this.__sms.slice()
		},
    //-------------------------------------------------------------------------------
		data: function() {//!
			if(arguments.length)	throw jsl.ParamError()//!

			return {
				protocol:	this.__ptc,
				scheme: 	this.__sch,
				host:			this.__hst,
				port:			this.__prt,
				path:			this.__pth,
				query:		this.__qry,
				fragment:	this.__frg,
				resource:	this.resource(),
				params:		this.params(),
				segments:	this.segments()
			}
		},
    //-------------------------------------------------------------------------------
		parent: function() {
			var url//!
			if(arguments.length)	throw jsl.ParamError()//!

			if(!this.__pth)
				url = this + ""

			else
				url =  this.__sch + "://" + this.__hst + (this.__prt ? ":" + this.__prt : "") + "/" +
							 this.__sms.slice(0, -1).join("/")

			return new jsl.Url(url)
		},
    //-------------------------------------------------------------------------------
		toString: function() {
			var url, ps, Url//!
			if(arguments.length)	throw jsl.ParamError()//!

			Url	= jsl.Url
			url = this.__sch + "://" + this.__hst
			url = url + (this.__prt !== Url.__dps[this.__sch] ? ":" + this.__prt : "")
			url = url + (this.__pth || this.__qry || this.__frg ? "/" : "")
			url = url + (this.__pth || "")

			if(this.__qry) {

				url += "?"
				ps	 = []

				for(var id in this.__pms)
					ps.push(id + "=" + this.__pms[id])

				url += ps.join("&")
			}

			if(this.__frg) url = url + "#" + this.__frg
			return url
		},
    //private
		//-------------------------------------------------------------------------------
		__parseScheme: function(s) {
			var v, Url, dt, rt

			Url	= jsl.Url
			dt 	= s.match(/^\s*([^:\s\/]+):\/\/(.*$)/)
			v		= s

			if(dt) {
				rt = jsl.Url.scheme(dt[1])
				if(rt) v = rt + dt[2]
			}

			return v
		},
		//-------------------------------------------------------------------------------
		__parseHost: function(s) {
			var dt

			//RFC1738
			//host        = hostname | hostnumber
			//hostname    = *[ domainlabel "." ] toplabel
			//domainlabel = alphadigit | alphadigit *[ alphadigit | "-" ] alphadigit
			//toplabel    = alpha | alpha *[ alphadigit | "-" ] alphadigit
			//alpha				= a-z | A-Z

			if(!(/^([a-z\d]([a-z\d\-]*[a-z\d])?\.)*[a-z]([a-z\d\-]*[a-z\d])?$/i).test(s)) {
				dt = s.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
				if(!dt || +dt[1] > 255 || +dt[2] > 255 || +dt[3] > 255) s = false
				else s = +dt[1] + "." + +dt[2] + "." + +dt[3] + "." + +dt[4]
			}

			return s
		},
		//-------------------------------------------------------------------------------
		__parsePort: function(s) {
			if(s > 65535)	s = false
			return s
		},
		//-------------------------------------------------------------------------------
		__parseQuery: function(s) {
			if(s && !(/^[^=]+=[^&]*(&[^=]+=[^&]*)*$/).test(s)) s = false
			return s
		}
	})
